import json
import math
import time
import uuid

import redis
from redis.client import Redis

mconn = Redis(host="192.168.35.113", port=6379, db=0, decode_responses=True)

QUIT = False


def send_sold_email_via_queue(conn, seller, item, price, buyer):
    data = {
        'seller_id': seller,
        'item_id': item,
        'price': price,
        'buyer_id': buyer,
        'time': time.time()
    }
    conn.rpush('queue:email', json.dumps(data))


def process_sold_email_queue(conn):
    while not QUIT:
        packed = conn.blpop(['queue:email'], 30)
        if not packed:
            continue
        to_send = json.loads(packed[1])
        try:
            fetch_data_and_send_sold_email(to_send)
        except Exception as err:
            print(err)
        else:
            print("send success:")


def fetch_data_and_send_sold_email(to_send):
    print("send:" + json.dumps(to_send))


def acquire_lock_with_timeout(conn, lockname, acquire_timeout=10, lock_timeout=10):
    identifier = str(uuid.uuid4())
    lockname = 'lock:' + lockname
    lock_timeout = int(math.ceil(lock_timeout))
    end = time.time() + acquire_timeout
    while time.time() < end:
        if conn.setnx(lockname, identifier):
            conn.expire(lockname, lock_timeout)
            return identifier
        elif not conn.ttl(lockname):
            conn.expire(lockname, lock_timeout)
        time.sleep(0.001)
    return False


def release_lock(conn, lockname, identifier):
    pipe = conn.pipeline(True)
    lockname = 'lock:' + lockname
    while True:
        try:
            pipe.watch(lockname)
            if pipe.get(lockname) == identifier:
                pipe.multi()
                pipe.delete(lockname)
                pipe.execute()
            pipe.unwatch()
            break
        except redis.exceptions.WatchError:
            pass
    return False


def execute_later(conn, queue, name, args, delay=0):
    identifire = str(uuid.uuid4())
    item = json.dumps(identifire, queue, name, args)
    if delay > 0:
        conn.zadd('delayed:', {item: time.time() + delay})
    else:
        conn.rpush('queue:' + queue, item)
    return identifire


def pull_queue(conn):
    while not QUIT:
        item = conn.zrange('delayed:', 0, 0, withscores=True)
        if not item or item[0][1] > time.time():
            time.sleep(0.01)
            continue
        item = item[0][0]
        ident, queue, function, args = json.loads(item)
        locked = acquire_lock_with_timeout(conn, ident)
        if not locked:
            continue
        if conn.zrem('delayed:', item):
            conn.rpush('queue:' + queue, item)
        release_lock(conn, ident, locked)


def worker_watch_queue(conn, queue, callbacks):
    while not QUIT:
        packed = conn.blpop([queue], 30)
        if not packed:
            continue
        name = packed[0]
        args = json.loads(packed[1])
        if name not in callbacks:
            continue
        callbacks[name](args)



# 按间距中的绿色按钮以运行脚本。
if __name__ == '__main__':
    send_sold_email_via_queue(mconn, "hyman", '衣服', '600', 'tom')
    worker_watch_queue(mconn, 'queue:email', {'queue:email': fetch_data_and_send_sold_email})
    execute_later(mconn, 'queue:email', '', '', 10)
    pull_queue(mconn)

# 访问 https://www.jetbrains.com/help/pycharm/ 获取 PyCharm 帮助
